import ij.ImagePlus;
import ij.gui.GenericDialog;
import ij.gui.ImageCanvas;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

/*
 * This ImageJ plugin demonstrates continuous mapping from one image (origIp) to
 * a new image (logPolarIp) using the current mouse position as the center
 * coordinate. Fist it queries the user for the parameters (to be extended).
 * The first mouse click on the source image activates the process. The
 * target image is continuously updated whenever the mouse is moved. The
 * process terminates when the mouse is clicked again. You may zoom into
 * any of the images while the process is running, coordinates should still
 * be correct.
 * Use IJ.log("...") to output debug information if necessary.
 * 
 * Author: W. Burger
 * Date: 2012-03-27
 */


public class A04_Log_Polar implements PlugInFilter, MouseListener, MouseMotionListener {
	static int ANGULAR_STEPS = 100;
	static int RADIAL_STEPS = 60;
	static double LOG_BASE = 1.2;

	ImageProcessor origIp;
	ImagePlus origIm;
	ImageCanvas origCanvas;
	ImageProcessor logPolarIp;
	ImagePlus logPolarIm;
	boolean active;

	int origWidth;
	double angStepSize, maxRadStep;
	
	public int setup(String arg0, ImagePlus img) {
		this.origIm = img;
		return DOES_8G + DOES_RGB + NO_CHANGES;	// does 8-bit and RGB images
	}

	public void run(ImageProcessor ip) {
		if (!getUserInput()) {
			return;
		}
		origIp = ip;
		origIp.setInterpolationMethod(ImageProcessor.BILINEAR);
		origWidth = origIp.getWidth();
		angStepSize = 2 * Math.PI / ANGULAR_STEPS;
		maxRadStep = Math.log(1 + origWidth) / Math.log(LOG_BASE);
		active = false;
		origCanvas = origIm.getCanvas();
		origCanvas.addMouseListener(this);
	}

	// ----------- YOUR WORK GOES IN HERE! ------------------------------------
	
	void repaintLogPolarImage(double xc, double yc) {
		if (logPolarIp == null) {
			logPolarIp = origIp.createProcessor(RADIAL_STEPS, ANGULAR_STEPS);
			logPolarIm = new ImagePlus("Log Polar Image", logPolarIp);
			logPolarIm.show();
		}
		
		double theta = 0;
		
		for (int ri=0; ri<RADIAL_STEPS; ri++)  {
			
			double r = Math.pow(LOG_BASE, ri * maxRadStep / RADIAL_STEPS) - 1;
						
			for (int ai=0; ai<ANGULAR_STEPS; ai++) {
			
				theta += angStepSize;
				
				double x = Math.cos(theta) * r + xc;
				double y = Math.sin(theta) * r + yc;
				
				int p = origIp.getPixelInterpolated(x, y);
				logPolarIp.putPixel(ri, ai, p);
			}
		}
		logPolarIm.updateAndDraw();
	}
	
	// -------------------------------------------------------------------------

	void activate() {
		if (!active) {
			origCanvas.addMouseMotionListener(this);
			active = true;
		}
	}

	void terminate() {
		origCanvas.removeMouseListener(this);
		origCanvas.removeMouseMotionListener(this);
	}

	// --------- mouse event handling --------------------

	public void mouseClicked(MouseEvent e) {
		if (active) 
			terminate();
		else 
			activate();
	}

	public void mouseMoved(MouseEvent e) {
		if (active) {
			double xc = origCanvas.offScreenXD(e.getX());
			double yc = origCanvas.offScreenYD(e.getY());
			repaintLogPolarImage(xc, yc);
		}
	}

	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mousePressed(MouseEvent e) {}
	public void mouseReleased(MouseEvent e) {}
	public void mouseDragged(MouseEvent e) {}

	// -------- user Dialog ------------------------------------

	boolean getUserInput() {
		GenericDialog gd = new GenericDialog("Log-Polar Mapping");
		gd.addNumericField("Radial steps", RADIAL_STEPS, 0);
		gd.addNumericField("Angular steps", ANGULAR_STEPS, 0);
		gd.addNumericField("Log base", LOG_BASE, 2);
		gd.addMessage("Click left in image\n to start/terminate\n mapping");
		gd.showDialog();
		if (gd.wasCanceled()) {
			return false;
		}
		RADIAL_STEPS = (int) gd.getNextNumber();
		ANGULAR_STEPS = (int) gd.getNextNumber();
		LOG_BASE = gd.getNextNumber();
		return true;
	}

}
